import { defineComponent, h, toRefs, reactive } from "vue"
import * as AntdComponent from "ant-design-vue";

const splitKey = /[.|\[]/;
const MODEL_REG = /\bv-model\b:?/i;
const DEFAULT_MODEL = "v-model"
const DEFAULT_VALUE_FILED = "modelValue"
const DEFAULT_EVENT = "update:modelValue";
// 提取model
const handler = {
  get(target, key) {
    if (typeof key == 'string') {
      let modelFiled;
      if (key.indexOf('[') > -1) {
        modelFiled = key.split('[')[0];
      } else if (key.indexOf('.') > -1) {
        modelFiled = key.split('.')[0];
      }

      if (modelFiled) {
        const model = Reflect.get(target, modelFiled);
        return model;
      }
    }
    return Reflect.get(target, key);
  }
};
// 首字母大写
function capitalizeFirstLetter(str) {
  if (typeof str !== 'string') {
    return "Please provide a valid string.";
  }

  return str.charAt(0).toUpperCase() + str.slice(1);
}
class MethodGen {
  constructor(ctx, source) {
    this.ctx = ctx;
    this.source = source;
  }
  toMethod() {
    const methods = {};
    for (let method in this.source) {
      methods[method] = (...args) => {
        const keys = Object.keys(this.ctx);
        const func = new Function(`
        const {${keys.join(',')}} = this;
        ${this.source[method]};
      `);

        func.call(Object.assign(this.ctx, methods), ...args)
      };
    }

    return methods;
  }
}
function getData(source, path) {
  if (typeof source.value === 'object') {
    const key = path.replace(source._key, "");
    return new Function(`return this${key}`).call(source.value)
  } else {
    return source.value
  }
}
function setData(source, path, value) {
  const key = path.replace(source._key, "");
  new Function(` return this${key} = arguments[0]`).call(source.value, value)
}
// render数据解析器
const resolveData = (params, dymicModel, config = {}) => {
  const { key, valueFiled = "value", updateEvent = "update:value" } = config;

  if (typeof dymicModel.value == 'object') {
    params[valueFiled] = getData(dymicModel, key);
    params.on[updateEvent] = [(val) => {
      setData(dymicModel, key, val)
    }];
  } else {
    params[valueFiled] = getData(dymicModel);
    params.on[updateEvent] = [(val) => dymicModel.value = val];
  }
  return params
}
// 解构
const resolveProps = (props, model) => {
  const keys = Object.keys(props).filter(key => key.indexOf(":") > -1);
  if (keys.length > 0) {
    keys.forEach(key => {
      props[key.replace(":", '')] = getModelData(model, props[key]);
    })
  }

  return props
}
const getDymicModel = (model, key) => model[key.split(splitKey)[0]]
const getModelData = (model, key) => getData(getDymicModel(model, key), key)

// 渲染器
export default defineComponent({
  props: {
    tree: {
      type: Object,
      default: () => { }
    }
  },
  setup(props, { expose }) {
    const model = new Proxy(toRefs(reactive(props.tree.data)), handler);
    const methodGen = new MethodGen(model, props.tree.methods);
    const methods = methodGen.toMethod();
    const ctx = {
      instance: model,
      methods
    }

    // 处理渲染函数
    const handlerRender = (obj, props) => {
      if (typeof obj.children === 'string') {
        let renderContent = obj.children;
        const arr = renderContent.split("{{").filter(i => i.indexOf("}}") > -1);

        if (arr?.length > 0) {
          arr.forEach(c => {
            const endIndex = c.indexOf("}}");
            const content = c.slice(0, endIndex);
            const key = c.slice(0, endIndex).trim();
            const data = getModelData(model, key);
            const replaceContent = "{{" + content.slice(0, endIndex) + "}}";
            renderContent = renderContent.replace(replaceContent, data)
          })
        }
        return renderContent;
      } else {
        return obj?.children?.map(c => getDomTree(c, props))
      }
    };

    const getDomTree = (obj, props) => {
      let component;
      const resolveComponent = AntdComponent[capitalizeFirstLetter(obj.component)];
      if (resolveComponent) {
        component = defineComponent(resolveComponent)
      } else {
        component = defineComponent(obj.component)
      }

      // 处理props
      if (obj.props) {
        resolveProps(obj.props, model)
      }

      if (typeof component === 'object') {
        return renderComponents(component, obj, ctx, props);
      } else {
        return h(component, {
          ...obj.props,
        }, {
          default: () => handlerRender(obj, props)
        })
      }
    }
    const renderComponents = (component, obj, ctx, props) => {
      const { instance: model, methods } = ctx;
      const params = {
        on: {}
      };

      if (obj) {
        const keys = Object.keys(obj).filter(key => MODEL_REG.test(key));
        keys.forEach(key => {
          const dymicModel = getDymicModel(model, obj[key]);

          if (key === DEFAULT_MODEL) {
            obj.updateEvent = DEFAULT_EVENT
            obj.valueFiled = DEFAULT_VALUE_FILED;
          } else {
            obj.updateEvent = "update:" + key.split(":")[1]
            obj.valueFiled = key.split(":")[1];
          }

          obj.key = obj[key];
          resolveData(params, dymicModel, obj)
        })
      }

      if (obj.on) {
        Object.entries(obj.on).forEach(attrs => {
          !params.on[attrs[0]] && (params.on[attrs[0]] = [])
          if (methods[attrs[1]]) {
            params.on[attrs[0]].push(methods[attrs[1]])
          } else {
            if (attrs[1].startsWith("(")) {
              const keys = Object.keys(model);
              const hook = new Function(
                `
                  const {${keys.join(',')}} = this;
                  const fn = ${attrs[1]};
                  fn.call(this, ...arguments);
                `
              );
              params.on[attrs[0]].push(hook.bind({ ...model }, props))
            }
          }
        })
      }

      Object.entries(params.on).forEach(attrs => {
        params[`on` + capitalizeFirstLetter(attrs[0])] = attrs[1]
      })
      delete params.on;

      let slots = {};
      if (obj.slots) {
        Object.entries(obj.slots).forEach(([key, value]) => {
          slots[key] = (props) => {
            return getDomTree.call(null, value, props)
          }
        })
      }

      return h(component, {
        ...obj.props,
        ...params,
      }, {
        default: () => handlerRender(obj, props),
        ...slots
      })
    }

    expose(ctx)
    if (props.tree.children) {
      return () => {
        const dom = getDomTree(props.tree);
        return dom
      }
    }

    return
  }
})